using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using FibMatrix.IO;
using FibMatrix.Schedule;
using FibMatrix.Utils;
using ICSharpCode.SharpZipLib.Core;
using ICSharpCode.SharpZipLib.Zip;
using UnityEngine;
using UnityEngine.Networking;

namespace FibMatrix
{
    public class ResourceUpdateManager
    {
        public enum UpdateResult
        {
            None = 0,
            Success = 1,
            Failed = 2,
        }
        private const string BLOCK_FILE_NAME = "Assets.block";
        private const string ASSET_CONFIG_CACHE_PREFIX = "AssetConfigCache";
        private const string SILENT_DOWNLOAD_PATCH_ID_KEY = "SilentDownloadPatchId";
        private const string SILENT_DOWNLOAD_PATCH_VER_KEY = "SilentDownloadPatchVer";
        private const string PATCH_ID_EXPRESSION = "Patch{0}_{1}.zip";
        
        public class UpdateProgressContext
        {
            public int downloadedFileNum;
            public int totalFileNum;
            public ulong downloadedBytes;
            public ulong totalBytes;
        }
        
        private class UpdateDownloadContext
        {
            public AssetNode node;
            public string tempStoragePath;
            public string tempStorageBlockPath;
            public string storagePath;
            public int retryNum;
            public bool resumeDownload;

            public UpdateDownloadContext(AssetNode n, string tsp, string tsbp, string sp, int rn, bool rd)
            {
                this.node = n;
                this.tempStoragePath = tsp;
                this.tempStorageBlockPath = tsbp;
                this.storagePath = sp;
                this.retryNum = rn;
                this.resumeDownload = rd;
            }
        }

        private class SilentDownloadPatchContext
        {
            private string _id;
            public string id => _id;

            private string _remoteVersion;

            public string remoteVersion => _remoteVersion;

            private Action<string, string> _completeCallback;

            private ResourceDownloader _downloader;
            private UnityWebRequest _request;
            private Coroutine _coroutine;

            public SilentDownloadPatchContext(string id, string remoteVersion, Action<string, string> completeCallback)
            {
                this._id = id;
                this._remoteVersion = remoteVersion;
                this._completeCallback = completeCallback;
            }
            
            public void Execute(string localVersion, string storagePath, string cdn, string remoteConfigUrl, Func<string, string, int> versionCompareFunc)
            {
                _coroutine = CoroutineProvider.instance.StartCoroutine(DoExecute(localVersion, storagePath, cdn, remoteConfigUrl, versionCompareFunc));
            }

            public void Dispose()
            {
                if (_request != null)
                {
                    _request.Abort();

                    try
                    {
                        _request.Dispose();
                    }
                    catch (Exception)
                    {
                        // ignored
                    }

                    _request = null;
                }

                if (_downloader != null)
                {
                    _downloader.Dispose();
                    _downloader = null;
                }

                if (_coroutine != null)
                {
                    CoroutineProvider.instance.StopCoroutine(_coroutine);
                    _coroutine = null;
                }

                _completeCallback = null;
            }

            private void DoComplete(bool setSuccess)
            {
                _coroutine = null;
                if (setSuccess)
                {
                    PlayerPrefs.SetString(SILENT_DOWNLOAD_PATCH_ID_KEY, _id);
                    PlayerPrefs.SetString(SILENT_DOWNLOAD_PATCH_VER_KEY, _remoteVersion);
                    if (_completeCallback != null)
                    {
                        _completeCallback(_remoteVersion, _id);
                        _completeCallback = null;
                    }
                }
            }

            private IEnumerator DoExecute(string localVersion, string storagePath, string cdn, string remoteConfigUrl, Func<string, string, int> versionCompareFunc)
            {
                string assetConfigStoragePath = Path.Combine(storagePath, ASSET_CONFIG_CACHE_PREFIX + _id);
                string assetConfigStorageBlock = assetConfigStoragePath + ".block";
                byte[] remoteConfigBytes = null;
                var waitForFiveSecond = new WaitForSeconds(5f);
                if (StorageManager.Exists(assetConfigStoragePath) && !StorageManager.Exists(assetConfigStorageBlock))
                {
                    remoteConfigBytes = StorageManager.ReadAllBytes(assetConfigStoragePath);
                    if (CommonUtil.GetBytesMD5(remoteConfigBytes) != _id)
                    {
                        StorageManager.DeleteFile(assetConfigStoragePath);
                        remoteConfigBytes = null;
                    }
                }
                
                if (remoteConfigBytes == null)
                {
                    if (!StorageManager.Exists(assetConfigStorageBlock))
                    {
                        using(StorageManager.CreateFile(assetConfigStorageBlock)){};
                    }
                    
                    do
                    {
                        _request = UnityWebRequest.Get(remoteConfigUrl);
                        UnityWebRequestAsyncOperation ao = _request.SendWebRequest();
                        yield return ao;

                        if (!_request.isHttpError && !_request.isNetworkError)
                        {
                            remoteConfigBytes = _request.downloadHandler.data;
                            try
                            {
                                _request.Dispose();
                            }
                            catch (Exception)
                            {
                                // ignored
                            }
                        }
                        else if (_request.isHttpError)
                        {
                            Debug.LogError($"Silent download {remoteConfigUrl} failed, responseCode {_request.responseCode}");
                            try
                            {
                                _request.Dispose();
                            }
                            catch (Exception)
                            {
                                // ignored
                            }
                            yield break;
                        }
                        
                        try
                        {
                            _request.Dispose();
                        }
                        catch (Exception)
                        {
                            // ignored
                        }

                        _request = null;

                        if (remoteConfigBytes != null)
                        {
                            if (CommonUtil.GetBytesMD5(remoteConfigBytes) != _id)
                            {
                                yield break;
                            }
                            StorageManager.WriteAllBytes(assetConfigStoragePath, remoteConfigBytes);
                            StorageManager.DeleteFile(assetConfigStorageBlock);
                            break;
                        }

                        yield return waitForFiveSecond;
                    } while (true);
                }

                yield return null;

                AssetConfig remoteConfig = AssetConfigSerializer.DeserializeAssetsConfig(remoteConfigBytes, true);
                
                if (remoteConfig == null)
                {
                    DoComplete(false);
                    yield break;
                }

                if (versionCompareFunc(localVersion, remoteConfig.version) >= 0)
                {
                    DoComplete(true);
                    yield break;
                }

                if (!remoteConfig.isPatchEmpty && remoteConfig.patchList.TryGetValue(
                    string.Format(PATCH_ID_EXPRESSION, remoteConfig.version, localVersion), out var patchNode))
                {
                    string tempStoragePath = Path.Combine(storagePath, patchNode.md5);
                    string tempStorageBlockPath = tempStoragePath + ".block";
                    if (StorageManager.Exists(tempStoragePath) && !StorageManager.Exists(tempStorageBlockPath))
                    {
                        DoComplete(true);
                    }
                    else
                    {
                        if (!StorageManager.Exists(tempStorageBlockPath))
                        {
                            using(StorageManager.CreateFile(tempStorageBlockPath)){};
                        }

                        do
                        {
                            _downloader = ResourceDownloader.StartNew(
                                ResourceHelper.GetAssetURL(cdn, patchNode.url, patchNode.md5), patchNode.size,
                                patchNode.compressedSize, patchNode.crc32, tempStoragePath, null, null, -1, true);

                            while (_downloader != null && !_downloader.isCompleted)
                            {
                                yield return null;
                            }

                            if (_downloader != null && string.IsNullOrEmpty(_downloader.error))
                            {
                                _downloader.Dispose();
                                _downloader = null;
                                StorageManager.DeleteFile(tempStorageBlockPath);
                                DoComplete(true);
                                yield break;
                            }
                            else
                            {
                                Debug.LogError($"download error:{_downloader?.error}");
                            }

                            yield return waitForFiveSecond;
                            
                        } while (true);
                    }
                }
                else
                {
                    DoComplete(true);
                }
            }
        }

        private const int DEFAULT_RETRY_COUNT = 3;
        private const int DEFAULT_MAX_DOWNLOAD_NUM = 4;
        private const int DEFAULT_TIMEOUT = 10;

        private Func<string, string, int> _versionCompareFunc;
        private Action<AssetConfig> _saveAssetConfigCallback;
        private bool _inUpdate = false;
        private Action<UpdateResult, string> _completeCallback;
        private Action<UpdateProgressContext> _progressCallback;
        private AssetConfig _localConfig;
        private int _retryCount;
        private string _persistDataPath;
        private string _temporaryCachePath;
        private string _cdn;
        private int _maxDownloadNum;
        private int _delayNotifier;
        private Coroutine _updateCoroutine;
        private ResourceDownloader[] _currentDownloaders;
        private bool _configured = false;
        private int _timeout = DEFAULT_TIMEOUT;
        private SilentDownloadPatchContext _sdpContext;

        public int timeout
        {
            get => _timeout;
            set => _timeout = value;
        }

        public string storagePath
        {
            get
            {
                return _persistDataPath;
            }
        }

        private static ResourceUpdateManager _instance = new ResourceUpdateManager();

        public static ResourceUpdateManager instance => _instance;

        public void Configure(string cdn, string persistDataPath, string temporaryCachePath, Action<AssetConfig> saveAssetConfigCallback, int maxDownloadNum = -1, int retryCount = -1, Func<string, string, int> versionCompareFunc = null)
        {
            this.Reset();
            _cdn = cdn;
            _persistDataPath = Path.Combine(persistDataPath, ResourcePathConst.REMOTE_ASSET_DIRECTORY);
            StorageManager.CreateDirectoryIfNotExist(Path.Combine(_persistDataPath, "PlaceHolder"));
            _temporaryCachePath = Path.Combine(temporaryCachePath, ResourcePathConst.TEMP_ASSET_DIRECTORY);
            StorageManager.CreateDirectoryIfNotExist(Path.Combine(_temporaryCachePath, "PlaceHolder"));
            StorageManager.CreateDirectoryIfNotExist(Path.Combine(_persistDataPath, ResourcePathConst.ASSEMBLIES_DIRECTORY_PREFIX));
            StorageManager.CreateDirectoryIfNotExist(Path.Combine(_persistDataPath, ResourcePathConst.BUNDLE_DIRECTORY_PREFIX));
            _maxDownloadNum = maxDownloadNum <= 0 ? DEFAULT_MAX_DOWNLOAD_NUM : maxDownloadNum;
            _retryCount = retryCount <= 0 ? DEFAULT_RETRY_COUNT : retryCount;
            if (versionCompareFunc == null)
            {
                _versionCompareFunc = AssetConfig.VersionCompare;
            }

            _saveAssetConfigCallback = saveAssetConfigCallback;
            _currentDownloaders = new ResourceDownloader[_maxDownloadNum];
            _configured = true;
        }

        public static bool ValidateUpdateComplete(string lastUpdateSavePath)
        {
            string blockFileFullPath = Path.Combine(lastUpdateSavePath, BLOCK_FILE_NAME);
            if (StorageManager.Exists(blockFileFullPath))
            {
                StorageManager.DeleteFile(blockFileFullPath);
                return false;
            }

            return true;
        }

        public void Start(AssetConfig local, string remoteConfigVer, string remoteConfigUrl, Action<UpdateProgressContext> progressCallback,
            Action<UpdateResult, string> completeCallback)
        {
            this.Start(local, remoteConfigVer, remoteConfigUrl, null, null, progressCallback, completeCallback);
        }
        
        public void Start(AssetConfig local, string remoteConfigVer, string remoteConfigUrl, string warmConfigVer, string warmConfigId, Action<UpdateProgressContext> progressCallback, Action<UpdateResult, string> completeCallback)
        {
            if (!_configured)
            {
                throw new InvalidOperationException("Start without configured");
            }
            if (_inUpdate)
            {
                _progressCallback = progressCallback;
                _completeCallback = completeCallback;
                return;
            }

            _inUpdate = true;
            _localConfig = local;
            _progressCallback = progressCallback;
            _completeCallback = completeCallback;
            if (_versionCompareFunc(local.version, remoteConfigVer) >= 0)
            {
                _delayNotifier = GlobalScheduler.CallOnceFrame(1, OnUpdateComplete, UpdateResult.None);
                return;
            }

            byte[] remoteConfigCacheBytes = null;
            if (remoteConfigVer == warmConfigVer && !string.IsNullOrEmpty(warmConfigId))
            {
                string assetConfigStoragePath = Path.Combine(_temporaryCachePath, ASSET_CONFIG_CACHE_PREFIX + warmConfigId);
                string assetConfigStorageBlock = assetConfigStoragePath + ".block";
                if (StorageManager.Exists(assetConfigStoragePath) && !StorageManager.Exists(assetConfigStorageBlock))
                {
                    remoteConfigCacheBytes = StorageManager.ReadAllBytes(assetConfigStoragePath);
                    if (CommonUtil.GetBytesMD5(remoteConfigCacheBytes) != warmConfigId)
                    {
                        StorageManager.DeleteFile(assetConfigStoragePath);
                        remoteConfigCacheBytes = null;
                    }
                }
            }

            _updateCoroutine = CoroutineProvider.instance.StartCoroutine(DoUpdate(remoteConfigUrl, remoteConfigCacheBytes));
        }

        public void SilentDownloadPatch(AssetConfig local, string remoteConfigVer, string remoteConfigUrl, string remoteConfigUniqueIdentifier, Action<string, string> completeCallback)
        {
            if (PlayerPrefs.GetString(SILENT_DOWNLOAD_PATCH_ID_KEY) == remoteConfigUniqueIdentifier && PlayerPrefs.GetString(SILENT_DOWNLOAD_PATCH_VER_KEY) == remoteConfigVer)
            {
                return;
            }

            if (_sdpContext != null)
            {
                if (_sdpContext.id == remoteConfigUniqueIdentifier && _sdpContext.remoteVersion == remoteConfigVer)
                {
                    return;
                }
                _sdpContext.Dispose();
                _sdpContext = null;
            }

            _sdpContext = new SilentDownloadPatchContext(remoteConfigUniqueIdentifier, remoteConfigVer, completeCallback);
            _sdpContext.Execute(local.version, _temporaryCachePath, _cdn, remoteConfigUrl, _versionCompareFunc);
        }

        public void Reset()
        {
            if (_sdpContext != null)
            {
                _sdpContext.Dispose();
                _sdpContext = null;
            }
            
            _configured = false;
            if (_inUpdate)
            {
                GlobalScheduler.RemoveLimitUpdator(ref _delayNotifier);

                if (_updateCoroutine != null)
                {
                    CoroutineProvider.instance.StopCoroutine(_updateCoroutine);
                    _updateCoroutine = null;
                }
            }

            _progressCallback = null;
            _completeCallback = null;
            _saveAssetConfigCallback = null;
            _versionCompareFunc = null;
            if (_currentDownloaders != null)
            {
                for (int i = 0; i < _maxDownloadNum; ++i)
                {
                    if (_currentDownloaders[i] != null)
                    {
                        _currentDownloaders[i].Dispose();
                        _currentDownloaders[i] = null;
                    }
                }
            }
        }

        private IEnumerator DoUpdate(string remoteConfigUrl, byte[] remoteConfigBytes)
        {
            if (remoteConfigBytes == null)
            {
                int tryCount = 0;
                do
                {
                    UnityWebRequest www = UnityWebRequest.Get(remoteConfigUrl);
                    UnityWebRequestAsyncOperation ao = www.SendWebRequest();
                    ulong lastDownloadedBytes = www.downloadedBytes;
                    float freezeTime = 0;
                    while (!ao.isDone)
                    {
                        yield return null;
                        if (_timeout > 0)
                        {
                            if (lastDownloadedBytes == www.downloadedBytes)
                            {
                                freezeTime += Time.unscaledDeltaTime;
                                if (freezeTime >= _timeout)
                                {
                                    break;
                                }
                            }
                            else
                            {
                                lastDownloadedBytes = www.downloadedBytes;
                                freezeTime = 0;
                            }
                        }
                    }

                    if (!ao.isDone || www.isHttpError || www.isNetworkError)
                    {
                        try
                        {
                            www.Dispose();
                        }
                        catch (Exception)
                        {
                            // ignored
                        }

                        ++tryCount;
                    }
                    else
                    {
                        remoteConfigBytes = www.downloadHandler.data;
                        try
                        {
                            www.Dispose();
                        }
                        catch (Exception)
                        {
                            // ignored
                        }

                        break;
                    }
                } while (tryCount < _retryCount);
            }

            AssetConfig remoteConfig = AssetConfigSerializer.DeserializeAssetsConfig(remoteConfigBytes, true);
            
            if (remoteConfig == null)
            {
                OnUpdateFailed("HotUpdate:Download remoteConfig at " + remoteConfigUrl + " failed");
                yield break;
            }

            if (_versionCompareFunc(_localConfig.version, remoteConfig.version) >= 0)
            {
                OnUpdateComplete(UpdateResult.None);
                yield break;
            }

            AssetConfig.FindUpdateAssets(_localConfig, remoteConfig, out var updateOrAddAssets, out var deleteAssets);
            if (updateOrAddAssets.Count == 0)
            {
                DeleteAssets(deleteAssets);
                _saveAssetConfigCallback(remoteConfig);
                yield break;
            }
            
            List<KeyValuePair<string, string>> copyItems =
                new List<KeyValuePair<string, string>>(updateOrAddAssets.Count);
            Queue<UpdateDownloadContext> needDownloadAssess = new Queue<UpdateDownloadContext>();
            UpdateProgressContext progressContext = new UpdateProgressContext();
            progressContext.totalFileNum = copyItems.Count;
            ulong totalDownloadSize = 0;
            int completeFileNum = 0;
            ulong completeBytes = 0;

            bool singleFileMode = false;
            
            if (MemoryEnvironment.HasValue("HOT_UPDATE_PATCH_ON") && !remoteConfig.isPatchEmpty && remoteConfig.patchList.TryGetValue(string.Format(PATCH_ID_EXPRESSION, remoteConfig.version, _localConfig.version), out var patchNode))
            {
                singleFileMode = true;
                string tempStoragePath = Path.Combine(_temporaryCachePath, patchNode.md5);
                string tempStorageBlockPath = tempStoragePath + ".block";
                string targetStoragePath = Path.Combine(_persistDataPath, patchNode.url);
                copyItems.Add(new KeyValuePair<string, string>(tempStoragePath, targetStoragePath));
                ulong assetSize = patchNode.compressed ? patchNode.compressedSize : patchNode.size;
                if (StorageManager.Exists(tempStoragePath) && !StorageManager.Exists(tempStorageBlockPath))
                {
                    ++completeFileNum;
                    completeBytes += assetSize;
                    totalDownloadSize += assetSize;
                }
                else
                {
                    totalDownloadSize += assetSize;
                    needDownloadAssess.Enqueue(new UpdateDownloadContext(patchNode, tempStoragePath, tempStorageBlockPath, targetStoragePath, 0, true));               
                }
            }
            else
            {
                for (int i = 0, l = updateOrAddAssets.Count; i < l; ++i)
                {
                    AssetNode node = updateOrAddAssets[i];
                    if (node.placeHolder)
                    {
                        continue;
                    }
                    string tempStoragePath = Path.Combine(_temporaryCachePath, node.md5);
                    string tempStorageBlockPath = tempStoragePath + ".block";
                    string targetStoragePath = Path.Combine(_persistDataPath, node.url);
                    copyItems.Add(new KeyValuePair<string, string>(tempStoragePath, targetStoragePath));
                    ulong assetSize = node.compressed ? node.compressedSize : node.size;
                    if (StorageManager.Exists(tempStoragePath) && !StorageManager.Exists(tempStorageBlockPath))
                    {
                        ++completeFileNum;
                        completeBytes += assetSize;
                        totalDownloadSize += assetSize;
                        continue;
                    }

                    totalDownloadSize += assetSize;
                    needDownloadAssess.Enqueue(new UpdateDownloadContext(node, tempStoragePath, tempStorageBlockPath, targetStoragePath, 0, false));
                }
            }
            
            progressContext.totalBytes = totalDownloadSize;

            progressContext.downloadedBytes = completeBytes;
            progressContext.downloadedFileNum = completeFileNum;

            ResourceDownloader[] downloaders = _currentDownloaders = new ResourceDownloader[_maxDownloadNum];
            do
            {
                ulong lastDownloadedBytes = progressContext.downloadedBytes;
                CalculateDownloaderProgress(downloaders, _maxDownloadNum, completeBytes, progressContext);
                if (lastDownloadedBytes != progressContext.downloadedBytes)
                {
                    OnUpdateProgress(progressContext);
                }

                string exceedDownloadTimesUrl = null;
                for (int i = 0; i < _maxDownloadNum; ++i)
                {
                    downloaders[i] = CheckOrCreateDownloader(downloaders[i], _cdn, needDownloadAssess, _timeout,
                        _retryCount, ref completeBytes, ref completeFileNum, out var exceedMaxDownloadTimes);
                    if (exceedMaxDownloadTimes)
                    {
                        exceedDownloadTimesUrl = downloaders[i].url;
                        break;
                    }
                }

                if (!string.IsNullOrEmpty(exceedDownloadTimesUrl))
                {
                    DisposeAllDownloaders(downloaders, _maxDownloadNum);
                    OnUpdateFailed("HotUpdate:Asset " + exceedDownloadTimesUrl + "download exceed maxRetryCount");
                    yield break;
                }

                if (!IsDownloaderRunning(downloaders, _maxDownloadNum))
                {
                    break;
                }

                progressContext.downloadedFileNum = completeFileNum;

                yield return null;
            } while (true);

            string copyLockFile = Path.Combine(_persistDataPath, BLOCK_FILE_NAME); 
            using(StorageManager.CreateFile(copyLockFile)){};
            DeleteAssets(deleteAssets);
            if (singleFileMode)
            {
                progressContext.downloadedBytes = progressContext.totalBytes = 0;
                
                ZipStrings.CodePage = Encoding.UTF8.CodePage;
                var iter = UncompressFromZip(copyItems[0].Key, _persistDataPath, progressContext);
                while (iter.MoveNext())
                {
                    yield return null;
                }
            }
            else
            {
                for (int i = 0, l = copyItems.Count; i < l; ++i)
                {
                    StorageManager.CopyFile(copyItems[i].Key, copyItems[i].Value, true);
                }    
            }
            _saveAssetConfigCallback(remoteConfig);
            StorageManager.DeleteFile(copyLockFile);
            OnUpdateComplete(UpdateResult.Success);
            StorageManager.DeleteDirectory(_temporaryCachePath, true);
        }
        
        public IEnumerator UncompressFromZip(string archivePath, string outFolder, UpdateProgressContext context) 
        {
            using(Stream fs = StorageManager.OpenRead(archivePath)) 
            using(ZipFile zf = new ZipFile(fs))
            {
                context.totalFileNum = (int)zf.Count;
                int completeNum = context.downloadedFileNum = 0;
                OnUpdateProgress(context);
                long lastTime = FibMatrix.RealTimer.elapsedMilliseconds;
                foreach (ZipEntry zipEntry in zf) {
                    if (!zipEntry.IsFile) {
                        // Ignore directories
                        continue;
                    }

                    if (FibMatrix.RealTimer.elapsedMilliseconds - lastTime > 100)
                    {
                        OnUpdateProgress(context);
                        yield return null;
                        lastTime = FibMatrix.RealTimer.elapsedMilliseconds;
                    }
                    String entryFileName = zipEntry.Name;
                    // to remove the folder from the entry:
                    //entryFileName = Path.GetFileName(entryFileName);
                    // Optionally match entrynames against a selection list here
                    // to skip as desired.
                    // The unpacked length is available in the zipEntry.Size property.

                    // Manipulate the output filename here as desired.
                    var fullZipToPath = Path.Combine(outFolder, entryFileName);
                    StorageManager.CreateDirectoryIfNotExist(fullZipToPath);

                    // 4K is optimum
                    var buffer = new byte[4096];

                    // Unzip file in buffered chunks. This is just as fast as unpacking
                    // to a buffer the full size of the file, but does not waste memory.
                    // The "using" will close the stream even if an exception occurs.
                    using(Stream zipStream = zf.GetInputStream(zipEntry))
                    using (Stream fsOutput = StorageManager.OpenWrite(fullZipToPath))
                    {
                        fsOutput.SetLength(0);
                        StreamUtils.Copy(zipStream, fsOutput, buffer);
                    }

                    ++context.downloadedFileNum;
                }
            }
        }

        static void CalculateDownloaderProgress(ResourceDownloader[] downloaders, int maxDownloadNum, ulong completedBytes,
            UpdateProgressContext context)
        {
            ulong inProgressDownloadSize = 0;
            for (int i = 0; i < maxDownloadNum; ++i)
            {
                inProgressDownloadSize += downloaders[i] != null ? (ulong)(downloaders[i].downloadSize * (double)downloaders[i].progress) : 0;
            }

            context.downloadedBytes = completedBytes + inProgressDownloadSize;
        }
        
        static void DisposeAllDownloaders(ResourceDownloader[] downloaders, int maxDownloadNum)
        {
            for (int i = 0; i < maxDownloadNum; ++i)
            {
                if (downloaders[i] != null)
                {
                    downloaders[i].Dispose();
                }
            }
        }
        
        static bool IsDownloaderRunning(ResourceDownloader[] downloaders, int maxDownloadNum)
        {
            for (int i = 0; i < maxDownloadNum; ++i)
            {
                if (downloaders[i] != null)
                {
                    return true;
                }
            }
            return false;
        }

        private static ResourceDownloader CheckOrCreateDownloader(ResourceDownloader currentDownloader, string cdn, Queue<UpdateDownloadContext> queue, int timeout, int maxRetryCount, ref ulong completedBytes, ref int completedFileNum, out bool exceedMaxDownloadTimes)
        {
            exceedMaxDownloadTimes = false;
            if (currentDownloader != null)
            {
                if (!currentDownloader.isCompleted)
                {
                    return currentDownloader;
                }
                
                UpdateDownloadContext context = currentDownloader.context as UpdateDownloadContext;
                if (!string.IsNullOrEmpty(currentDownloader.error))
                {
                    ++context.retryNum;
                    if (context.retryNum >= maxRetryCount)
                    {
                        Debug.LogError(currentDownloader.error);
                        exceedMaxDownloadTimes = true;
                        return currentDownloader;
                    }
                    queue.Enqueue(context);
                }
                else
                {
                    completedBytes += currentDownloader.downloadSize;
                    ++completedFileNum;
                    File.Delete(context.tempStorageBlockPath);
                }
            }
            if (queue.Count > 0)
            {
                UpdateDownloadContext data = queue.Dequeue();
                AssetNode node = data.node;
                if (!File.Exists(data.tempStorageBlockPath))
                {
                    using(File.Create(data.tempStorageBlockPath)){};
                }
                return ResourceDownloader.StartNew(ResourceHelper.GetAssetURL(cdn, node.url, node.md5), node.size, node.compressedSize, node.crc32, data.tempStoragePath, null, data, timeout, data.resumeDownload);
            }

            return null;
        }

        private void DeleteAssets(List<AssetNode> deletedAssets)
        {
            for (int i = 0, l = deletedAssets.Count; i < l; ++i)
            {
                AssetNode node = deletedAssets[i];
                if (node.placeHolder)
                {
                    continue;
                }
                string path = Path.Combine(_persistDataPath, node.url);
                if (File.Exists(path))
                {
                    File.Delete(path);
                }
            }
        }

        private void OnUpdateComplete(object obj)
        {
            _inUpdate = false;
            Action<UpdateResult, string> temp = _completeCallback;
            _completeCallback = null;
            temp((UpdateResult)obj, null);
        }

        private void OnUpdateProgress(UpdateProgressContext context)
        {
            if (_progressCallback != null)
            {
                try
                {
                    _progressCallback(context);
                }
                catch (Exception e)
                {
                    UnityEngine.Debug.LogException(e);
                }
            }
        }

        private void OnUpdateFailed(string error)
        {
            _inUpdate = false;
            Action<UpdateResult, string> temp = _completeCallback;
            _completeCallback = null;
            temp(UpdateResult.Failed, error);
        }
    }
}